﻿namespace EFS.RC.CC.Sessions.OfficeService.Operators
{
	/// <summary>
	/// Operator transfer in session
	/// </summary>
	internal class Transfer : EFS.RC.CC.Sessions.OfficeService.Operators.Operator
	{
		#region Composition

		// Impersonated URI for B2B call
		private System.String impersonatedUri = null;

		// Office conversation
		private Microsoft.Rtc.Collaboration.Conversation officeConversation;

		// Inbound call (first leg of B2B)
		private Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall transferredAudioVideoCall = null;

		// office call
		private Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall officeConversationAudioVideoCall = null;

		// back to back call into office conversation
		private Microsoft.Rtc.Collaboration.BackToBackCall officeConversationB2BCall;

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="conversation"></param>
		/// <param name="remoteEndpoint"></param>
		public Transfer(EFS.RC.CC.Presences.Operators.Presence operatorPresence, EFS.RC.CC.Sessions.OfficeService.Customers.Customer customerSession, Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall transferredAudioVideoCall)
			: base(operatorPresence, customerSession)
		{
			// check
			EFS.Common.Verify.Reference(transferredAudioVideoCall, "transferredAudioVideoCall");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "ResponseGroup:ctor", OperatorPresence.User.SIP, GetType());

			// keep call
			this.transferredAudioVideoCall = transferredAudioVideoCall;
			this.transferredAudioVideoCall.Conversation.ApplicationContext = this;

			// get events
			this.transferredAudioVideoCall.Conversation.StateChanged += OnConversationStateChanged;
		}

		#endregion

		#region Usage

		/// <summary>
		/// Describe this object into a nice formated string
		/// </summary>
		/// <returns></returns>
		public override System.String Describe()
		{
			return System.String.Format(System.String.Format("EFS.RC.CC.Sessions.OfficeService.Operators.Transfer [{0}] [{1}]", OperatorPresence.Describe(), CustomerSession.Describe()));
		}

		/// <summary>
		/// Impersonated uri
		/// </summary>
		public override System.String ParticipantEndPointUri
		{
			get { return impersonatedUri; }
		}

		/// <summary>
		/// Remote conversation id
		/// </summary>
		public override System.String RemoteConversationId
		{
			get { return transferredAudioVideoCall.Conversation.Id; }
		}

		/// <summary>
		/// Get participant end point
		/// </summary>
		public override Microsoft.Rtc.Collaboration.ParticipantEndpoint ParticipantRemoteEndPoint
		{
			get
			{
				// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
				lock (syncAccess)
				{
					// is here
					return transferredAudioVideoCall.RemoteEndpoint;
				}
			}
		}

		/// <summary>
		/// Handle incomming call from conference
		/// </summary>
		/// <param name="call"></param>
		internal void HandleReplacedCall(Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall transferredAudioVideoCall)
		{
			// check
			EFS.Common.Verify.Reference(transferredAudioVideoCall, "transferredAudioVideoCall");

			// check not already handled
			// ~ commented for performance ~ EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Dial In Session is not Started");
				}

				// get app
				EFS.RC.CC.Applications.EndPoints.OfficeService.Operators operatorsOfficeServiceEndPointApplication = CustomerSession.
					CustomersOfficeServiceEndPointApplication.
					ApplicationPresence.
					EndPointsPresencesRegistrar.
					OperatorOfficeServiceEndPointPresence.
					PresenceEndPointApplication;

				// get agent for an external invite
				EFS.RC.CC.Presences.Operators.Presence presence = operatorsOfficeServiceEndPointApplication.OperatorsTeam.GetAgentBySIP(transferredAudioVideoCall.RemoteEndpoint.Participant.Uri);

				// check
				if (null == presence)
				{
					// make anon operator
					presence = new EFS.RC.CC.Presences.Operators.Anonymous(transferredAudioVideoCall.RemoteEndpoint.Participant.Uri);
				}

				// make a session
				EFS.RC.CC.Sessions.OfficeService.Operators.Transfer operatorTransferSession = new EFS.RC.CC.Sessions.OfficeService.Operators.Transfer(presence, CustomerSession, transferredAudioVideoCall);
				operatorTransferSession.AsinkEstablishedDelegate.Register(OnOperatorTransferSessionEstablished);

				// start agent session
				operatorTransferSession.BeginStartup();
			}
		}

		#endregion

		#region Startup

		/// <summary>
		/// Startup
		/// </summary>
		protected override void SetupSession()
		{
			// log
			EFS.EventLog.Log.LogInfo("SetupSession", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "SetupSession", this);

			// register opertor session
			CustomerSession.OfficeServiceConference.SetOperatorSession(this);

			// join office conference
			BeginAsyncProcess2(JoinOfficeConference);
		}

		/// <summary>
		/// Join Office conference
		/// </summary>
		/// <param name="agentHunter"></param>
		private void JoinOfficeConference()
		{
			// log
			EFS.EventLog.Log.LogInfo("JoinOfficeConference", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "JoinOfficeConference", this);

			// get conference uri
			System.String conferenceUri = OfficeServiceConference.ConferenceUri;

			// get end point
			Microsoft.Rtc.Collaboration.ApplicationEndpoint applicationEndpoint = CustomerSession.CustomersOfficeServiceEndPointApplication.CollaborationApplicationEndpoint;

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// impersonated uri
				impersonatedUri = System.String.Format("sip:{0}@{1}", System.Guid.NewGuid().ToString(), applicationEndpoint.DefaultDomain);

				// log impersonation
				EFS.EventLog.Log.LogInfo(System.String.Format("Impersonate officeConversation {0}", impersonatedUri), this);

				// make office conversation
				officeConversation = new Microsoft.Rtc.Collaboration.Conversation(CustomerSession.CustomersOfficeServiceEndPointApplication.CollaborationApplicationEndpoint);
				officeConversation.Impersonate(impersonatedUri, null, transferredAudioVideoCall.RemoteEndpoint.Participant.DisplayName);
				officeConversation.ApplicationContext = this;
				officeConversation.StateChanged += OnOfficeConversationStateChanged;

				// conference options
				Microsoft.Rtc.Collaboration.ConferenceJoinOptions conferenceJoinOptions = new Microsoft.Rtc.Collaboration.ConferenceJoinOptions();
				conferenceJoinOptions.JoinMode = Microsoft.Rtc.Collaboration.JoinMode.Default;

				// join
				officeConversation.ConferenceSession.BeginJoin(
					conferenceUri,
					conferenceJoinOptions,
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(EndJoinOfficeConference));
			}
		}

		/// <summary>
		/// End establish
		/// </summary>
		/// <param name="result"></param>
		private void EndJoinOfficeConference(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("EndJoinOfficeConference", this);

			try
			{
				// end join
				officeConversation.ConferenceSession.EndJoin(ar);

				// office service converence is now joined, get customer in
				BeginAsyncProcess2(MakeB2BCall);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Make back2back call for customer into conference
		/// </summary>
		private void MakeB2BCall()
		{
			EFS.EventLog.Log.LogInfo("MakeB2BCall", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// handle transfer and FW
				transferredAudioVideoCall.TransferReceived += OnAudioVideoCallTransferReceived;
				transferredAudioVideoCall.Forwarded += OnAudioVideoCallForwarded;

				// Make B2B
				officeConversationAudioVideoCall = new Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall(officeConversation);
				officeConversationAudioVideoCall.TransferReceived += OnAudioVideoCallTransferReceived;
				officeConversationAudioVideoCall.Forwarded += OnAudioVideoCallForwarded;

				// make call
				officeConversationB2BCall = new Microsoft.Rtc.Collaboration.BackToBackCall(
						new Microsoft.Rtc.Collaboration.BackToBackCallSettings(transferredAudioVideoCall),
						new Microsoft.Rtc.Collaboration.BackToBackCallSettings(officeConversationAudioVideoCall));

				// start call
				officeConversationB2BCall.BeginEstablish(
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(OnEndEstablishB2BCall));
			}
		}

		/// <summary>
		/// End B2B call
		/// </summary>
		/// <param name="ar"></param>
		private void OnEndEstablishB2BCall(System.IAsyncResult ar)
		{
			try
			{
				// end
				officeConversationB2BCall.EndEstablish(ar);

				// established
				AsinkEstablishedDelegate.Start();
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Start-up complete
		/// </summary>
		/// <param name="ar"></param>
		private void OnStartupComplete(EFS.Common.Async.AsyncProcess ap)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnStartupComplete", this);

			try
			{
				// check
				ap.Throw();

				// log
				EFS.EventLog.Log.LogInfo("OnStartupComplete SUCCCESS", this);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Agent session established
		/// </summary>
		private void OnOperatorTransferSessionEstablished(EFS.RC.CC.Sessions.OfficeService.IParticipant officeServiceSessionParticipant)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnSessionTerminated", this);

			// check
			EFS.Common.Verify.Reference(officeServiceSessionParticipant, "officeServiceSessionParticipant");

			// cast
			EFS.RC.CC.Sessions.OfficeService.Operators.Operator operatorSession = officeServiceSessionParticipant as EFS.RC.CC.Sessions.OfficeService.Operators.Operator;

			// check
			EFS.Common.Verify.Reference(operatorSession, "operatorSession");

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// Transfer DDI
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void OnAudioVideoCallTransferReceived(object sender, Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCallTransferReceivedEventArgs e)
		{
			try
			{
				// make tranfer 
				EFS.RC.CC.Tools.Functions.Operator.Trasfer transfer = new EFS.RC.CC.Tools.Functions.Operator.Trasfer(this, e.TransferDestination);

				// start transfer
				transfer.BeginTransfer();

				// accept
				e.Accept(null);
			}
			catch (System.Exception ex)
			{
				// accept
				e.Decline();

				// log
				EFS.EventLog.Log.LogException(ex, this);
			}
		}

		/// <summary>
		/// FW DDI
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void OnAudioVideoCallForwarded(object sender, Microsoft.Rtc.Collaboration.CallForwardReceivedEventArgs e)
		{
			try
			{
				// make tranfer 
				EFS.RC.CC.Tools.Functions.Operator.Forward forward = new EFS.RC.CC.Tools.Functions.Operator.Forward(this, e.ForwardDestination);

				// start transfer
				forward.BeginForward();
			}
			catch (System.Exception ex)
			{
				// accept
				e.Decline();

				// log
				EFS.EventLog.Log.LogException(ex, this);
			}
		}

		/// <summary>
		/// State changed for agent conersation
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		private void OnConversationStateChanged(object sender, Microsoft.Rtc.Signaling.StateChangedEventArgs<Microsoft.Rtc.Collaboration.ConversationState> args)
		{
			try
			{
				// SAL
				EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "OnConversationStateChanged", args.State, this);

				// check terminating
				if (args.State == Microsoft.Rtc.Collaboration.ConversationState.Terminating)
				{
					// log
					EFS.EventLog.Log.LogInfo("Shutting down - office conversation is beyign terminated", this);

					// start 
					BeginShutdown();
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// State changed for office conersation
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		private void OnOfficeConversationStateChanged(object sender, Microsoft.Rtc.Signaling.StateChangedEventArgs<Microsoft.Rtc.Collaboration.ConversationState> args)
		{
			try
			{
				// SAL
				EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "OnOfficeConversationStateChanged", args.State, this);

				// check terminating
				if (args.State == Microsoft.Rtc.Collaboration.ConversationState.Terminating)
				{
					// log
					EFS.EventLog.Log.LogInfo("Shutting down - office conversation is beyign terminated", this);

					// start 
					BeginShutdown();
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}
		}

		#endregion

		#region Shutdown

		/// <summary>
		/// Cleanup
		/// </summary>
		protected override void CleanupSession()
		{
			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "CleanupSession", this);

			// make termination
			EFS.RC.CC.Tools.Functions.Termination termination = new EFS.RC.CC.Tools.Functions.Termination(this);

			// remove delegate
			officeConversation.StateChanged -= OnOfficeConversationStateChanged;
			transferredAudioVideoCall.Conversation.StateChanged -= OnConversationStateChanged;

			// terminate all
			termination.Terminate(officeConversationB2BCall, OnEndTerminateB2BCall);
		}

		/// End B2B
		/// </summary>
		/// <param name="ar"></param>
		private void OnEndTerminateB2BCall(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("OnEndTerminateB2BCall", this);

			try
			{
				// end terminate
				officeConversationB2BCall.EndTerminate(ar);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		#endregion
	}
}
